package com.changgou.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.changgou.goods.feign.SkuFeign;
import com.changgou.goods.pojo.Sku;
import com.changgou.search.dao.SkuEsMapper;
import com.changgou.search.pojo.SkuInfo;
import com.changgou.search.service.SkuService;
import com.changgou.util.Result;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;

/*****
 * @Author: 黑马训练营
 * @Description: com.changgou.search.service.impl
 ****/
@Service
public class SkuServiceImpl implements SkuService {

    //调用Feign
    @Autowired
    private SkuFeign skuFeign;

    //注入Dao
    @Autowired
    private SkuEsMapper skuEsMapper;

    /***
     * ElasticsearchTemplate:可以实现ES索引库的操作
     */
    @Autowired
    private ElasticsearchTemplate esTemplate;


    /****
     * 搜索实现
     * @param searchMap
     * @return
     */
    @Override
    public Map search(Map<String, String> searchMap) {
       //搜索条件封装
        NativeSearchQueryBuilder builder = buildBasicQuery(searchMap);

       //搜索执行
        Map resultMap = searchList(builder);

        //分页数据获取
        //当前页
        resultMap.put("pageNum",builder.build().getPageable().getPageNumber()+1);
        //每页显示条数
        resultMap.put("pageSize",builder.build().getPageable().getPageSize());


        //显示的分类的分组条件查询
        //List<String> categoryNameList =searchCategoryList(builder);
        //resultMap.put("categoryList",categoryNameList);

        //品牌条件加载
        //List<String> brandList = searchBrandList(builder);
        //resultMap.put("brandList",brandList);

        //加载所有的规格条件
        //Map<String, Set<String>> specMap = searchSpec(builder);
        //resultMap.putAll(specMap);

        //调用合并后的分组方法
        Map groupMapData = groupList(builder,searchMap);
        resultMap.putAll(groupMapData);
        return resultMap;
    }

    /***
     * 分组查询合并方法
     */
    public Map groupList(NativeSearchQueryBuilder builder, Map<String,String> searchMap){
        //搜索条件中如果brand为空，则表示用户没有将品牌作为搜索条件，需要加载品牌分组数据，用于页面条件显示
        if(StringUtils.isEmpty(searchMap.get("brand"))){
            //1.添加分组的域，并且给分组域指定别名  terms("给当前分组取别名").field("分组的域")
            builder.addAggregation(AggregationBuilders.terms("skuBrandName").field("brandName"));
        }

        //搜索条件中如果category为空，则表示用户没有将分类作为搜索条件，需要加载分类分组数据，用于页面条件显示
        if(StringUtils.isEmpty(searchMap.get("category"))){
            //1.添加分组的域，并且给分组域指定别名  terms("给当前分组取别名").field("分组的域")
            builder.addAggregation(AggregationBuilders.terms("skuCategory").field("categoryName"));
        }

        //规格需要查询
        builder.addAggregation(AggregationBuilders.terms("skuSpec").field("spec.keyword"));

        //2.执行分组搜索，获取搜索的结果集   queryForPage("搜索条件","返回结果需要转换的对象字节码")
        AggregatedPage<SkuInfo> page = esTemplate.queryForPage(builder.build(), SkuInfo.class);
        Aggregations aggregations = page.getAggregations();

        //存储所有分组数据
        Map groupMap = new HashMap();

        //品牌数据
        if(StringUtils.isEmpty(searchMap.get("brand"))){
            List<String> brandList = getGroupData(aggregations,"skuBrandName");
            groupMap.put("brandList",brandList);
        }
        //分类数据
        if(StringUtils.isEmpty(searchMap.get("category"))){
            List<String> categoryList = getGroupData(aggregations,"skuCategory");
            groupMap.put("categoryList",categoryList);
        }
        //规格数据
        List<String> specList = getGroupData(aggregations,"skuSpec");

        //2.将所有spec封装成一个Map<String,Set<String>>
        Map<String, Set<String>> specMap = specPutAll(specList);
        groupMap.put("specList",specMap);
        return groupMap;
    }

    /***
     * 获取指定别名分组数据
     */
    public List<String> getGroupData(Aggregations aggregations,String groupName){
        //3.获取分组数据，解析结果集  brandPage.getAggregations().get("根据别名获取分组的结果集数据")
        List<String> groupList = new ArrayList<String>();
        StringTerms brandTerms = aggregations.get(groupName);

        for (StringTerms.Bucket bucket : brandTerms.getBuckets()) {
            groupList.add(bucket.getKeyAsString());
        }
        return groupList;
    }



    /****
     * 将所有spec封装成一个Map<String,Set<String>>
     */
    public Map<String,Set<String>> specPutAll(List<String> specList){
        //定义一个存储所有规格数据的Map  specMapList
        Map<String,Set<String>> specMapList = new HashMap<String,Set<String>>();
        
        //对集合进行遍历   spec={"学习内容":"框架学习","包装内容":"视频"}
        for (String spec : specList) {
            //将字符串类型的spec转成Map
            Map<String,String> specMap = JSON.parseObject(spec,Map.class);

            //将每个spec存储到Map(specMapList)中
            for (Map.Entry<String, String> entry : specMap.entrySet()) {
                //去除重复->key和value都需要去除重复
                //先获取当前enrty的key和value
                String key = entry.getKey();        //学习分类
                String value = entry.getValue();

                //从Map(specMapList)获取一下对应的key的值  specValues
                Set<String> specValues = specMapList.get(key);

                //如果对应的值(specValues)为空，则直接把当前enry的数据添加到Map(specMapList)
                if(specValues==null){
                    specValues = new HashSet<String>();
                }
                //如果对应的值不为空，则获取对应的值，并将当前entry的value追加到specValues中
                specValues.add(value);

                //再将当前组装Map添加到Map(specMapList)中
                specMapList.put(key,specValues);
            }
        }

        //返回Map(specMapList)
        return specMapList;
    }




    /***
     * 搜索方法
     */
    public Map searchList(NativeSearchQueryBuilder builder){
        /***
         * 执行搜索，获取到结果集
         * esTemplate.queryForPage("要搜索的条件",搜索的结果集需要转换的对象的字节码)
         */
        //Page<SkuInfo> skuInfoPage = esTemplate.queryForPage(builder.build(),SkuInfo.class);

        /*****
         * 实现高亮搜索
         * 1.设置高亮域
         * 2.设置前缀、后缀
         */
        HighlightBuilder.Field field = new HighlightBuilder
                .Field("name")                      //高亮域设置
                .preTags("<span style=\"color:red\">")   //前缀
                .postTags("</span>")                      //后缀
                .fragmentSize(100);                        //碎片大小
        //添加高亮域信息(开启高亮)
        builder.withHighlightFields(field);

        /***
         * 实现搜索
         *  1.将非高亮数据搜索出来
         *  2.将高亮数据搜索出来
         *  3.需要做的事：将非高亮数据替换成高亮数据
         *  SearchResultMapper:搜索后的结果集映射数据，可以手动获取所有数据，并可以手动处理所有数据
         */
        AggregatedPage<SkuInfo> skuInfoPage = esTemplate.queryForPage(builder.build(), SkuInfo.class, new SearchResultMapper() {
            @Override
            public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
                //存储所有结果集
                List<T> list = new ArrayList<T>();

                /***
                 * 将非高亮数据替换成高亮
                 */
                SearchHits hits = response.getHits();
                for (SearchHit hit : hits) {
                    //获取非高亮数据
                    String skuInfoJson = hit.getSourceAsString();//获取当前数据的JSON字符串,可以使用FastJSON转换成SkuInfo
                    SkuInfo skuInfo = JSON.parseObject(skuInfoJson,SkuInfo.class);

                    //获取高亮数据
                    Map<String, HighlightField> highlightFields = hit.getHighlightFields(); //获取所有域的高亮数据集合

                    if(highlightFields!=null){
                        //获取指定域的高亮数据
                        HighlightField highlightField = highlightFields.get("name");
                        //从highlightField中获取高亮数据的值  highlightField.fragments
                        if(highlightField!=null && highlightField.getFragments()!=null){
                            //取出数据->取出第1条记录作为高亮值
                            Text text = highlightField.getFragments()[0];
                            //将skuInfo中的name的非高亮数据换成高亮值
                            skuInfo.setName(text.toString());
                        }
                    }
                    list.add((T) skuInfo);
                }
                return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits());
            }
        });

        /***
         * 解析结果集
         * 1:获取结果集列表:skuInfoPage.getContent();
         * 2:获取中记录数:skuInfoPage.getTotalElements();
         */
        List<SkuInfo> contents = skuInfoPage.getContent();
        long totalElements = skuInfoPage.getTotalElements();
        //将结果集封装成Map
        Map resultMap = new HashMap();
        resultMap.put("rows",contents);
        resultMap.put("totalElements",totalElements);
        return resultMap;
    }

    /****
     * 用于封装所有搜索条件
     */
    public NativeSearchQueryBuilder buildBasicQuery(Map<String,String> searchMap){
        /***
         * 封装搜索条件
         *      NativeSearchQueryBuilder对象，用于封装搜索条件
         *      需要封装搜索条件：根据关键词搜索
         */
        NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();

        /***
         * 条件组合查询
         *  BoolQueryBuilder:实现多条件组合查询
         *      must        AND
         *      must not    NOT
         *      should      OR
         */
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();

        //判断用户是否输入了关键词
        if(searchMap!=null){
            if(!StringUtils.isEmpty(searchMap.get("keywords"))){
                /***
                 * 封装：搜索name域中有指定关键词的数据
                 * builder.withQuery():封装搜索条件
                 * QueryBuilders.matchQuery("要匹配的域","要搜索的关键词")
                 */
                //builder.withQuery(QueryBuilders.matchQuery("name",searchMap.get("keywords")));
                queryBuilder.must(QueryBuilders.matchQuery("name",searchMap.get("keywords")));
            }

            /***
             * 分类搜索 : category   完整的词 词条搜索
             */
            if(!StringUtils.isEmpty(searchMap.get("category"))){
                /***
                 * 1：指定搜索的域
                 * 2：指定搜索的数据
                 */
                queryBuilder.must(QueryBuilders.termQuery("categoryName",searchMap.get("category")));
            }


            /***
             * 品牌搜索 : brand      完整词汇
             */
            if(!StringUtils.isEmpty(searchMap.get("brand"))){
                queryBuilder.must(QueryBuilders.termQuery("brandName",searchMap.get("brand")));
            }

            /***
             * 规格查询
             */
            //循环Map
            for (Map.Entry<String, String> entry : searchMap.entrySet()) {
                //获取key，如果key以spec_开始，则说明当前为规格查询
                if(entry.getKey().startsWith("spec_")){//spec_网络
                    //组装条件
                    String key = entry.getKey().substring(5);
                    //queryBuilder.must(QueryBuilders.termQuery("specMap.网络.keyword","联通4G"));
                    queryBuilder.must(QueryBuilders.termQuery("specMap."+key+".keyword",entry.getValue()));
                }
            }

            /***
             * 价格区间查询   :  price
             * 1.接收所有价格数据，去掉中文字符  元和以上
             * 2:根据价格值中的-进行分割
             * 3:分割之后，如果有第1个值x，则实现  x<price
             * 4:如果分割之后有第2个值y，则实现 price<=y
             */
            String price = searchMap.get("price");
            if(!StringUtils.isEmpty(price)){
                //接收所有价格数据，去掉中文字符  元和以上
                price = price.replace("元","").replace("以上","");
                //根据价格值中的-进行分割
                String[] priceArrays = price.split("-");
                //分割之后，如果有第1个值x，则实现  x<price
                queryBuilder.must(QueryBuilders.rangeQuery("price").gt(Integer.parseInt(priceArrays[0])));
                //如果分割之后有第2个值y，则实现 price<=y
                if(priceArrays.length>1){
                    queryBuilder.must(QueryBuilders.rangeQuery("price").lte(Integer.parseInt(priceArrays[1])));
                }
            }

            /***
             * 排序
             * 1:接收前端传入的排序域     sortField=
             * 2:接收前端传入的排序类型   sortRule =DESC/ASC
             */
            String sortField = searchMap.get("sortField");
            String sortRule = searchMap.get("sortRule");
            if(!StringUtils.isEmpty(sortField) && !StringUtils.isEmpty(sortRule)){
                /***
                 * 1:排序的域
                 * 2:排序的类型
                 */
                builder.withSort(SortBuilders.fieldSort(sortField).order(SortOrder.valueOf(sortRule)));
            }
        }

        //分页
        int pageNo=pageConverter(searchMap);//当前要跳转的页码
        int pageSize=10;//每页查询多少条数据
        //分页查询
        builder.withPageable(PageRequest.of(pageNo-1,pageSize));

        //添加BoolQueryBuider
        builder.withQuery(queryBuilder);
        return builder;
    }

    /****
     * 获取当前页 : pageNum
     */
    public int pageConverter(Map<String,String> searchMap){
        try {
            int pageNum = Integer.parseInt(searchMap.get("pageNum"));
            return pageNum;
        } catch (Exception e) {
        }
        return 1;
    }






    //==========================================================下面的方法属于备份方法==========================================================
    /****
     * 搜索实现
     * @param searchMap
     * @return
     */
    public Map searchBackup(Map<String, String> searchMap) {
        /***
         * 封装搜索条件
         *      NativeSearchQueryBuilder对象，用于封装搜索条件
         *      需要封装搜索条件：根据关键词搜索
         */
        NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
        //判断用户是否输入了关键词
        if(searchMap!=null){
            if(!StringUtils.isEmpty(searchMap.get("keywords"))){
                /***
                 * 封装：搜索name域中有指定关键词的数据
                 * builder.withQuery():封装搜索条件
                 * QueryBuilders.matchQuery("要匹配的域","要搜索的关键词")
                 */
                builder.withQuery(QueryBuilders.matchQuery("name",searchMap.get("keywords")));
            }
        }

        /***
         * 执行搜索，获取到结果集
         * esTemplate.queryForPage("要搜索的条件",搜索的结果集需要转换的对象的字节码)
         */
        Page<SkuInfo> skuInfoPage = esTemplate.queryForPage(builder.build(),SkuInfo.class);

        /***
         * 解析结果集
         * 1:获取结果集列表:skuInfoPage.getContent();
         * 2:获取中记录数:skuInfoPage.getTotalElements();
         */
        List<SkuInfo> contents = skuInfoPage.getContent();
        long totalElements = skuInfoPage.getTotalElements();


        /****
         * 根据分类分组查询
         * 1:指定分组搜索的域
         * 2:执行分组搜索，获取分组结果集
         * 3:取出结果集封装成List集合
         */
        //1:指定分组搜索的域  AggregationBuilders:分组查询条件构建起
        //                    AggregationBuilders.terms("给分组查询的结果取个别名，用于获取结果集").field("需要分组查询的域")
        builder.addAggregation(AggregationBuilders.terms("skuCategory").field("categoryName"));   //添加聚合(分组)操作

        //2:执行分组搜索，获取分组结果集
        AggregatedPage<SkuInfo> aggregatedPage = esTemplate.queryForPage(builder.build(), SkuInfo.class);

        //3:取出结果集封装成List集合,取数据需要根据别名获取
        List<String> categoryNameList = new ArrayList<String>();
        StringTerms categoryTerms = aggregatedPage.getAggregations().get("skuCategory");
        for (StringTerms.Bucket bucket : categoryTerms.getBuckets()) {
            //获取分组后的一条记录
            String categoryName = bucket.getKeyAsString();
            categoryNameList.add(categoryName);
        }

        //将结果集封装成Map
        Map resultMap = new HashMap();
        resultMap.put("categoryList",categoryNameList);
        resultMap.put("rows",contents);
        resultMap.put("totalElements",totalElements);
        return resultMap;
    }

    /***
     * 导入数据到索引库中
     */
    @Override
    public void importSku() {
        //使用skuFeign调用goods微服务获取List<Sku>数据
        Result<List<Sku>> skuResult = skuFeign.findByStatus("1");
        List<Sku> skuList = skuResult.getData();

        //将List<Sku>封装成List<SkuInfo> ->FastJSON  ①将List<Sku>->JSON  ②将JSON转成List<SkuInfo>
        List<SkuInfo> skuinfoList = JSON.parseArray(JSON.toJSONString(skuList), SkuInfo.class);
        for (SkuInfo skuInfo : skuinfoList) {
            //spec  {"手机屏幕尺寸":"5寸","网络":"联通4G","颜色":"红","测试":"实施","机身内存":"16G","存储":"16G","像素":"300万像素"}
            String spec = skuInfo.getSpec();
            //将spec转成Map
            Map<String,Object> specMap = JSON.parseObject(spec,Map.class);
            skuInfo.setSpecMap(specMap);
        }

        //调用Dao将数据导入到ES索引库中
        skuEsMapper.saveAll(skuinfoList);
    }


    /****
     * 规格加载处理
     */
    public Map<String, Set<String>> searchSpec(NativeSearchQueryBuilder builder){
        //1.加载所有复合条件商品的spec
        //1.1根据spec分组查询
        //指定一个分组的域，并且给该分组查询取一个别名  size(1000000)//查询多少条记录的数据
        builder.addAggregation(AggregationBuilders.terms("skuSpec").field("spec.keyword").size(10000));

        //执行分组搜索，获取结果集
        AggregatedPage<SkuInfo> specPage = esTemplate.queryForPage(builder.build(), SkuInfo.class);

        //对结果集进行解析
        List<String> specList = new ArrayList<String>();
        StringTerms brandTerms = specPage.getAggregations().get("skuSpec");

        for (StringTerms.Bucket bucket : brandTerms.getBuckets()) {
            specList.add(bucket.getKeyAsString());
        }

        //2.将所有spec封装成一个Map<String,Set<String>>
        Map<String, Set<String>> specMap = specPutAll(specList);

        return specMap;
    }


    /***
     * 分组搜索
     */
    public List<String> searchCategoryList(NativeSearchQueryBuilder builder){
        /****
         * 根据分类分组查询
         * 1:指定分组搜索的域
         * 2:执行分组搜索，获取分组结果集
         * 3:取出结果集封装成List集合
         */
        //1:指定分组搜索的域  AggregationBuilders:分组查询条件构建起
        //                    AggregationBuilders.terms("给分组查询的结果取个别名，用于获取结果集").field("需要分组查询的域")
        builder.addAggregation(AggregationBuilders.terms("skuCategory").field("categoryName"));   //添加聚合(分组)操作

        //2:执行分组搜索，获取分组结果集
        AggregatedPage<SkuInfo> aggregatedPage = esTemplate.queryForPage(builder.build(), SkuInfo.class);

        //3:取出结果集封装成List集合,取数据需要根据别名获取
        List<String> categoryNameList = new ArrayList<String>();
        StringTerms categoryTerms = aggregatedPage.getAggregations().get("skuCategory");
        for (StringTerms.Bucket bucket : categoryTerms.getBuckets()) {
            //获取分组后的一条记录
            String categoryName = bucket.getKeyAsString();
            categoryNameList.add(categoryName);
        }
        return categoryNameList;
    }

    /****
     * 品牌加载
     * return List<String>:品牌名称集合
     */
    public List<String> searchBrandList(NativeSearchQueryBuilder builder){
        //1.添加分组的域，并且给分组域指定别名  terms("给当前分组取别名").field("分组的域")
        builder.addAggregation(AggregationBuilders.terms("skuBrandName").field("brandName"));

        //2.执行分组搜索，获取搜索的结果集   queryForPage("搜索条件","返回结果需要转换的对象字节码")
        AggregatedPage<SkuInfo> brandPage = esTemplate.queryForPage(builder.build(), SkuInfo.class);

        //3.获取分组数据，解析结果集  brandPage.getAggregations().get("根据别名获取分组的结果集数据")
        List<String> brandList = new ArrayList<String>();
        StringTerms brandTerms = brandPage.getAggregations().get("skuBrandName");

        for (StringTerms.Bucket bucket : brandTerms.getBuckets()) {
            brandList.add(bucket.getKeyAsString());
        }
        return brandList;
    }

}
